
package com.studiofortress.sf.util.structure.loading;

import com.studiofortress.sf.util.structure.FadeInActor;
import com.studiofortress.sf.graphics.display.Controls;
import com.studiofortress.sf.graphics.display.GraphicsDisplay;
import com.studiofortress.sf.graphics.GLTexture;
import com.studiofortress.sf.graphics.GraphicsGL;
import com.studiofortress.sf.structure.Actor;
import com.studiofortress.sf.structure.World;

/**
 * This is the class for all the code that should occur before the game plays.
 * It pre-loads the textures and the next game. At the same time it shows the
 * logo with it fading in and out. This is to help cover up the loading time. It
 * will also add a new FadingActor to the new world so it will fade in.
 * 
 * @author Joseph Lenton
 */
public class FadingLoadingWorld<G extends GraphicsGL, A extends Actor<? super G>> extends LoadingWorld<G, A>
{
    private static final long FADE_TIME = 1000;
    private static final long QUICK_FADE_TIME = 250;
    
    private static final long FADE_IN_GAME_TIME = 3000;
    private static final long INITIAL_TIME = 1000;
    
    private static final int FADE_IN_STATE = 2;
    private static final int SHOW_STATE = 3;
    private static final int FADE_OUT_STATE = 4;
    private static final int END_STATE = 6;

    private long fadeTime;

    private int state;
    private World nextWorld;

    // This is the time when the switch time is calculated.
    // This allows the switch time to be recalculated.
    private long initialSwitchTime;
    // This is the time when the state will change to the next switch.
    private long switchTime;
    private long lastTime;

    private final GLTexture logo;

    /**
     * The size of this loading world will be the same as the size of the canvas
     * in the given display.
     * @param display The display to base this worlds width and height on.
     * @param logo The logo to show whilst loading, cannot be null.
     */
    public FadingLoadingWorld(GraphicsDisplay display, GLTexture logo)
    {
        this(
                display.getCanvas().getWidth(),
                display.getCanvas().getHeight(),
                logo);
    }

    /**
     * @param width The width of this world.
     * @param height The height of this world.
     * @param logo The logo to display, cannot be null.
     */
    public FadingLoadingWorld(int width, int height, GLTexture logo)
    {
        super(width, height);

        if (logo == null) {
            throw new IllegalArgumentException("The given logo cannot be null.");
        }
        
        fadeTime = FADE_TIME;
        this.logo = logo;
        nextWorld = null;
        state = 0;
    }
    
    /**
     * Sets the next world that should follow this loading screen.
     * @param nextWorld The world that will follow this one.
     */
    protected void setNextWorld(World nextWorld)
    {
        this.nextWorld = nextWorld;
    }

    /**
     * @return Null if no next world has yet been made, otherwise the World that will be run after this has finished loading.
     */
    public World getNextWorld()
    {
        return nextWorld;
    }

    /**
     * 
     */
    @Override
    public void act()
    {
        if (fadeTime != QUICK_FADE_TIME) {
            Controls controls = getControls();
            if (controls.isAnyKeyDown() ||
                    controls.isMouseDown(Controls.MOUSE_LEFT) ||
                    controls.isMouseDown(Controls.MOUSE_RIGHT) ||
                    controls.isMouseDown(Controls.MOUSE_MIDDLE))
            {
                fadeTime = QUICK_FADE_TIME;
                if (switchTime != 0) {
                    switchTime = initialSwitchTime + fadeTime;
                }
            }
        }
        
        if (switchTime == 0) {
            switchTime = System.currentTimeMillis()+INITIAL_TIME;
        } else {
            lastTime = System.currentTimeMillis();
            if (lastTime > switchTime) {
                initialSwitchTime = lastTime;
                switchTime = lastTime+fadeTime;
                state++;
            }
            
            if (!isEmpty()) {
                loadAction();
            } else if (state >= END_STATE) {
                World gameWorld = getNextWorld();
                if (gameWorld != null) {
                    gameWorld.addActor(new FadeInActor(FADE_IN_GAME_TIME));
                    getMainLoop().setWorld(gameWorld);
                }
            }
        }
    }

    /**
     * Paints the logo to the screen. It's faded in, then faded out.
     * @param g
     */
    @Override
    public void paint(G g)
    {
        if (FADE_IN_STATE <= state && state <= FADE_OUT_STATE) {
            int alpha;
            if (state == FADE_IN_STATE) {
                alpha = ensureBounds(
                        255 - (int)(((double)(switchTime-lastTime) / (double)fadeTime)*255),
                        0,
                        255);
            } else if (state == SHOW_STATE) {
                alpha = 255;
            } else if (state == FADE_OUT_STATE) {
                alpha = ensureBounds(
                        (int)(((double)(switchTime-lastTime) / (double)fadeTime)*255),
                        0,
                        255);
            } else {
                return;
            }

            g.setColor(255, 255, 255, alpha);
            g.drawTexture(
                    logo,
                    (getWidth()-logo.getWidth())/2,
                    (getHeight()-logo.getHeight())/2);
        }
    }

    /**
     * Given a value, this checks if it's outside of the lower and upper bounds.
     * If it is then the lower or upper value is returned. Otherwise if the
     * value is between the bounds then the value is returned.
     * @param value
     * @param lower
     * @param upper
     * @return
     */
    private int ensureBounds(int value, int lower, int upper)
    {
        if (value < lower) {
            return lower;
        } else if (value > upper) {
            return upper;
        } else {
            return value;
        }
    }
}
